import {
  Box,
  DictSelect,
  FieldEffect,
  RefProp,
  useCall,
  useEffectState,
  usePubSub,
  usePubSubState,
  useRaf,
  useSimpleMemo,
  Void,
} from '@/components';
import { FORM_ITEM_KEYS } from '@/constants/componentKeys';
import { isEmpty, isEqualDeep, isEqualMap, isEqualStrictDeep, pick, uid } from '@/utils';
import { invert, pull } from '@/utils/fp';
import {
  Cascader,
  Checkbox,
  DatePicker,
  Form,
  Input,
  InputNumber,
  Radio,
  Select,
  Switch,
  TimePicker,
  TreeSelect,
} from 'antd';
import clsx from 'clsx';
import moment from 'moment';
import React, {
  createContext,
  forwardRef,
  memo,
  useContext,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react';
import { concatName, FormContext } from '.';
import SimpleEditor from './SimpleEditor';
import SimpleUpload, { NamedUpload } from './SimpleUpload';

FlexGroup = memo(FlexGroup);

function FlexGroup(props) {
  return <Box component={Input.Group} flex dir='row' {...props} />;
}

BoxField = memo(BoxField);

function BoxField({ component, ...props }) {
  const Comp = FieldTypes[component] ?? component ?? Input;
  return <Box component={Comp} {...props} />;
}

Plain = memo(Plain);

function Plain({ value, className }) {
  return <span className={clsx('ant-form-text', className)}>{value}</span>;
}

function NamedRange({
  endName,
  value: valueProp,
  mode,
  picker = 'date',
  format = 'YYYY-MM-DD HH:mm:ss',
  onChange,
  ...rest
}) {
  const fieldName = useFieldName();
  const nameOfEnd = useSimpleMemo(concatName(concatName(fieldName, '../'), endName ?? 'end'));
  const pubsub = usePubSub();
  const endValue = usePubSubState(pubsub);
  const [startValue, setStartValue] = useEffectState(valueProp);
  const [start, end] = useSimpleMemo([startValue, endValue]);
  const inputValue = useMemo(() => [start != null ? moment(start) : null, end != null ? moment(end) : null], [
    start,
    end,
  ]);

  console.log({ start, end, endName });
  const handleChange = (value) => {
    const [start, end] = value || [];
    const type = picker === 'date' ? 'day' : picker;
    const startValue = start?.startOf(type).format(format) ?? null;
    if (typeof onChange === 'function') {
      onChange(startValue);
    } else {
      setStartValue(startValue);
    }

    const endValue = end?.endOf(type).format(format) ?? null;
    console.log({ startValue, endValue });
    pubsub.update(endValue);
  };

  return (
    <>
      <FieldEffect name={nameOfEnd} pubsub={pubsub} />
      <DatePicker.RangePicker {...rest} value={inputValue} onChange={handleChange} />
    </>
  );
}

const booleanComp = (Comp) => (props) => {
  const { value, ...rest } = props;
  if (typeof value === 'boolean') {
    rest.checked = value;
  } else {
    rest.value = value;
  }
  return <Comp {...rest} />;
};

export const FieldTypes = {
  group: FlexGroup,
  textarea: Input.TextArea,
  'input-number': InputNumber,
  select: Select,
  tree: TreeSelect,
  cascade: Cascader,
  checkbox: booleanComp(Checkbox),
  'checkbox-group': Checkbox.Group,
  radio: booleanComp(Radio),
  'radio-group': Radio.Group,
  switch: booleanComp(Switch),
  date: DatePicker,
  time: TimePicker,
  range: DatePicker.RangePicker,
  'named-range': NamedRange,

  dict: DictSelect,
  upload: SimpleUpload,
  'named-upload': NamedUpload,
  editor: SimpleEditor,

  hidden: Void,
  plain: Plain,
  box: BoxField,
};

const FieldContext = createContext({
  name: null,
  form: null,
  item: null,
  itemProps: null,
  children: null,
});

/** 在FieldComponent内渲染的组件可获得当前字段的name */
export function useFieldName() {
  return useContext(FieldContext).name;
}

export function useFieldCtx(key) {
  return useContext(FieldContext)[key];
}

const propsMerge = (...objs) => {
  const ret = objs.reduce((last, next) => ({
    ...last,
    ...next,
    className: clsx(last?.className, next?.className),
    style: { ...last?.style, ...next?.style },
  }));
  if (isEmpty(ret?.style)) delete ret.style;
  return ret;
};
const getName = (name) => (Array.isArray(name) ? name.join('.') : name);

HiddenComp = forwardRef(HiddenComp);

function HiddenComp(props, ref) {
  const { pubsub } = useContext(FieldContext);
  const state = usePubSubState(pubsub);
  const name = getName(props.name);

  const onChange = useCall(props.onChange);
  const value = useSimpleMemo({ value: props.value, onChange, ...props.state });

  pubsub?.update(new Map(state).set(name, value));

  return <span ref={ref} style={{ display: 'none' }} />;
}

HiddenField = memo(forwardRef(HiddenField), isEqualStrictDeep);

export function HiddenField(props, forward) {
  const ref = useRef();
  const formItemDomRef = useRef();
  const [state, setState] = useState(null);

  useImperativeHandle(
    forward,
    () => ({
      state,
      ref,
    }),
    [state],
  );

  useEffect(() => {
    formItemDomRef.current = ref.current?.closest('.ant-form-item');
  }, []);

  // 获取item状态
  useRaf(() => {
    /** @type {HTMLElement} item */
    const item = formItemDomRef.current;
    if (item) {
      const help = item.querySelector('.ant-form-item-explain')?.innerText ?? null;
      const status =
        item.className.match(/-(has|is)-(?<status>success|error|warning|validating)(\s|$)/)?.groups?.status ?? '';
      const newState = { validateStatus: status, error: status === 'error' ? help : null };
      if (!isEqualDeep(newState, state)) {
        setState(newState);
      }
    }
  });

  return (
    <Form.Item {...props} style={{ display: 'none' }}>
      <HiddenComp ref={ref} name={props.name} state={state} />
    </Form.Item>
  );
}

export function FieldProvider({ children, ...rest }) {
  const pubsub = usePubSub(invert(isEqualMap), new Map());
  const context = useSimpleMemo({ pubsub, ...rest });

  return <FieldContext.Provider value={context} children={children} />;
}

const concat = (...items) => items.reduce((ret, i) => [...ret, ...(Array.isArray(i) ? i : [i])], []);
const mapSpan = (object) => Object.keys(object).reduce((ret, key) => ({ ...ret, [key]: { span: object[key] } }), {});

function useItemCol(isChild) {
  const childConf = useMemo(
    () => ({
      label: null,
      labelCol: { xs: { span: 0 } },
      wrapperCol: { xs: { span: 24 } },
      style: {
        display: 'inline-block',
        margin: 0,
      },
    }),
    [],
  );

  let { labelSpan = 6, wrapperSpan = 18 } = useContext(FormContext);
  if (typeof labelSpan === 'number') labelSpan = { xs: labelSpan };
  if (typeof wrapperSpan === 'number') wrapperSpan = { xs: wrapperSpan };
  const contextLabelCol = mapSpan(labelSpan);
  const contextWrapperCol = mapSpan(wrapperSpan);

  const contextConf = { labelCol: contextLabelCol, wrapperCol: contextWrapperCol };

  return useSimpleMemo(isChild ? childConf : contextConf);
}

FieldComponent = memo(FieldComponent, isEqualStrictDeep);

function FieldComponent(props) {
  const key = useRef(uid()).current;
  let { form, onField, onItem } = useContext(FormContext);
  const fieldPropsRef = useRef();
  const { isChild = false, index, type, getItemProps, itemProps: itemProp, fields, getFieldProps, ...rest } = props;

  const fieldOptions = onField([props, index, form, fieldPropsRef], getFieldProps);
  const itemOptions = onItem([props, index, form], getItemProps, itemProp);

  const [itemPickProps, fieldPickProps] = useSimpleMemo(pick(rest, FORM_ITEM_KEYS));

  const itemCol = useItemCol(isChild);

  let Component = typeof type === 'string' ? FieldTypes[type] : type;

  if (!Component) Component = Input;
  const render = useCall((props) => <Component {...props} />);

  const state = usePubSubState(useContext(FieldContext).pubsub);

  let subProps = [null, null];
  if (isChild) {
    subProps = pick(state.get(getName(props.name)) ?? {}, FORM_ITEM_KEYS);
  }

  const itemProps = useSimpleMemo(propsMerge(itemPickProps, itemCol, itemOptions));
  const fieldProps = useSimpleMemo(
    propsMerge(
      fieldPickProps,
      fieldOptions,
      Component === Input && type !== 'input' ? { type } : null,
      subProps[1],
      isChild ? null : { style: { width: '100%' } },
    ),
  );

  let element;

  const itemName = useFieldName() ?? itemProps.name;

  if (Array.isArray(fields) && fields.length) {
    const children = [];

    const items = [];

    fields.forEach((fieldItem, fIndex) => {
      const [{ name: subItemNameConf, ...subItemProps }, subFieldProps] = pick(
        fieldItem,
        pull('className', 'style')(FORM_ITEM_KEYS),
      );
      const subItemName = concatName(itemName, subItemNameConf);
      items.push(<HiddenField key={fIndex} name={subItemName} {...subItemProps} />);
      children.push(
        <FieldComponent key={fIndex} name={subItemName} {...subFieldProps} index={concat(index, fIndex)} isChild />,
      );
    });

    element = (
      <React.Fragment key={key}>
        {items}
        <Form.Item {...itemProps}>
          <RefProp ref={fieldPropsRef} refKey render={render} {...fieldProps} children={children} />
        </Form.Item>
      </React.Fragment>
    );
  } else if (isChild) {
    element = <RefProp ref={fieldPropsRef} refKey render={render} {...fieldProps} />;
  } else {
    element = (
      <React.Fragment key={key}>
        <Form.Item {...itemProps}>
          <RefProp ref={fieldPropsRef} refKey render={render} {...fieldProps} />
        </Form.Item>
      </React.Fragment>
    );
  }

  return <FieldProvider name={itemProps.name} item={props} form={form} itemProps={itemProps} children={element} />;
}

export default FieldComponent;
